package de.stoneageracer.vehicles;

import com.bulletphysics.dynamics.vehicle.WheelInfo;
import com.jme3.asset.AssetManager;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.bullet.objects.VehicleWheel;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.bullet.util.Converter;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import de.stoneageracer.core.RacingApplication;
import java.util.ArrayList;

/**
 * 
 * @author Basti
 */
public class Car extends VehicleControl{
    
    /**
     * defines which weapon is selected at the moment
     */
    private int selectedWeapon =0;
    /**
     * Weapons that are attached to the car at the moment!
     */
    private ArrayList<Weapon> weapons = new ArrayList();
    /**
     * Value to steer the first car (dont touch)
     */
    private float steeringValue = 0;
    /**
     * Value to accelerate the first car (dont touch)
     */
    private float accelerationValue = 0;
    /**
     * Acceleration speed of the Car
     */
    private float acceleration=8000;
    /**
     * Weight of the Car
     */
    private float weight = 4000;
    /**
     * the Node all the VehicleParts are attached to
     */
    private Node partsNode = new Node();
    /**
     * the node containing all model-details
     */
    public Node node = null;
    /**
     * location where your model is safed (ususally an ogre-xml-file or a scene)
     */
    public String modelUrl = null;
    /**
     * the name of the Car "body" spatial in your scene/model
     */
    public String carUrl = "Car";
    /**
     * the name of the Car "front right wheel" spatial in your scene/model
     */
    public String wheelFrontRight = "WheelFrontRight";
    /**
     * the name of the Car "front left wheel" spatial in your scene/model
     */
    public String wheelFrontLeft = "WheelFrontLeft";
    /**
     * the name of the Car "back right wheel" spatial in your scene/model
     */
    public String wheelBackRight = "WheelBackRight";
    /**
     * the name of the Car "back left wheel" spatial in your scene/model
     */
    public String wheelBackLeft = "WheelBackLeft";
    
    /**
     * the RacingApplication the Car is used in
     */
    private RacingApplication ra;
    /**
     * creates a Car. By default with the following values:
     * public String carUrl = "Car";
     * public String wheelFrontRight = "WheelFrontRight";
     * public String wheelFrontLeft = "WheelFrontLeft";
     * public String wheelBackRight = "WheelBackRight";
     * public String wheelBackLeft = "WheelBackLeft";
     * @param raceApp the RacingApplication in which you want your car to be used
     * @param modelUrl location where your model is safed (ususally an ogre-xml-file or a scene)
     */
    public Car(RacingApplication raceApp, String modelUrl){
        this.modelUrl = modelUrl;
        ra = raceApp;
        
    }
    
    /**
     * adds a steerable playerCar to the given node
     * @param node the Node on which you want to add your Car
     * @param x x-position to add the car
     * @param y y-position to add the car
     * @param z z-position to add the car
     * @return returns the car (this)
     */
    public Car addTo(Node node,float x,float y,float z){
        return buildPlayer(x,y,z);
    }
    
    /**
     * initialises CollisionShape of the car
     * @param carHull
     * @param mass
     */
    public void init(CollisionShape carHull,float mass){
        collisionShape = carHull;
        this.mass = mass;
        rebuildRigidBody();
    }
    
    /**
     * adds an "animal" at the given position with the given direction to the car
     * @param animal a Geometry representing the animal (not used yet) 
     * @param position the position to add the animal
     * @param direction the direction of the animal
     * @return returns a VehicleWheel
     * TODO: add animal
     */
    public VehicleWheel addAnimal(Geometry animal, Vector3f position, Vector3f direction) {
        
        BoundingBox box = (BoundingBox)animal.getModelBound();
        
        float radius = box.getYExtent();
        
        Vector3f connectionPoint = position;
        Vector3f axle = new Vector3f(direction.y,direction.x,direction.z);
        float suspensionRestLength = .02f;
        float wheelRadius = radius;
        boolean isFrontWheel = true;
        
        VehicleWheel wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
        
        if (vehicle != null) {
            WheelInfo info = vehicle.addWheel(Converter.convert(connectionPoint), Converter.convert(direction), Converter.convert(axle),
                    suspensionRestLength, wheelRadius, tuning, isFrontWheel);
            wheel.setWheelInfo(info);
        }
        wheel.setFrictionSlip(tuning.frictionSlip);
        wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm);
        wheel.setSuspensionStiffness(tuning.suspensionStiffness);
        wheel.setWheelsDampingCompression(tuning.suspensionCompression);
        wheel.setWheelsDampingRelaxation(tuning.suspensionDamping);
        wheel.setMaxSuspensionForce(tuning.maxSuspensionForce);
        wheels.add(wheel);
        if (debugShape != null) {
            detachDebugShape();
        }
//        updateDebugShape();
        return wheel;
    }
    
    /**
     * sets a new Weight for the car
     * @param w the new Weight (standart = 4000)
     */
    public void setWeight(float w){
        weight = w;
    }
    
    /**
     * gets the Weight of the Car (standart = 4000)
     * @return the Weight as a float
     */
    public float getWeight(){
        return weight;
    }
    
    /**
     * sets a new AccelerationValue for the car
     * @param a the new AccelerationValue (standart = 8000)
     */
    public void setAcceleration(float a){
        acceleration = a;
    }
    
    /**
     * gets the AccelerationValue of the Car (standart = 8000)
     * @return the AccelerationValue as a float
     */
    public float getAcceleration(){
        return acceleration;
    }
    
    /**
     * builds the Player(with all VehicleControl possibilities and meshs)
     * @param x the x-Value for positioning the car
     * @param y the y-Value for positioning the car
     * @param z the z-Value for positioning the car
     * @return returns the car (this)
     */
    public Car buildPlayer(float x, float y, float z) {
                
        AssetManager assetManager = ra.getAsset();
        
        
        float stiffness = 180.0f;//200=f1 car
        float compValue = 0.2f; //(lower than damp!)
        float dampValue = 0.3f;

        //Load model and get chassis Geometry
        Node carNode = (Node)assetManager.loadModel(modelUrl);
        
        carNode.setShadowMode(ShadowMode.CastAndReceive);
        Geometry chasis = ra.findGeom(carNode, carUrl);
        BoundingBox box = (BoundingBox) chasis.getModelBound();
        
        //Using only the following collBox for collision instead of chasis increases FPS but results in unrealistic collisions
        //Geometry collBox = new Geometry("collisionBox", new Box(box.getXExtent(),box.getYExtent(),box.getZExtent()));
        
        //Create a hull collision shape for the chassis
        CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis);

        //Create a vehicle control
        //VehicleControl myCar = new VehicleControl(carHull, mass);
        
        init(carHull,weight);
        
        carNode.addControl(this);


        //Setting default values for wheels
        setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
        setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
        setSuspensionStiffness(stiffness);
        setMaxSuspensionForce(10000);


        //Create four wheels and add them at their locations
        //note that our fancy car actually goes backwards..
        Vector3f wheelDirection = new Vector3f(0, -1, 0);
        Vector3f wheelAxle = new Vector3f(-1, 0, 0);

        
        Geometry wheel_fr = ra.findGeom(carNode, wheelFrontRight);
        wheel_fr.center();
        box = (BoundingBox) wheel_fr.getModelBound();
        float wheelRadius = box.getYExtent();
        float back_wheel_h = (wheelRadius * 1.7f) - 1f;
        float front_wheel_h = (wheelRadius * 1.9f) - 1f;
        //car.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
                //wheelDirection, wheelAxle, 0.2f, wheelRadius, true);

        addAnimal(wheel_fr, box.getCenter(), wheelDirection);
        
        Geometry wheel_fl = ra.findGeom(carNode, wheelFrontLeft);
        wheel_fl.center();
        box = (BoundingBox) wheel_fl.getModelBound();
        //car.addWheel(wheel_fl.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
                //wheelDirection, wheelAxle, 0.2f, wheelRadius, true);

        addAnimal(wheel_fl, box.getCenter(), wheelDirection);

        Geometry wheel_br = ra.findGeom(carNode, wheelBackRight);
        wheel_br.center();
        box = (BoundingBox) wheel_br.getModelBound();
        addWheel(wheel_br.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);


        Geometry wheel_bl = ra.findGeom(carNode, wheelBackLeft);
        wheel_bl.center();
        box = (BoundingBox) wheel_bl.getModelBound();
        addWheel(wheel_bl.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);
        
        getWheel(2).setFrictionSlip(4);
        getWheel(3).setFrictionSlip(4);
        
        if(ra.player == null){
            ra.player = this;
        }
        
        setPhysicsLocation(new Vector3f(x,y,z));
        
        //carNode.attachChild(createEmitter(new Vector3f(0,0,0), ColorRGBA.DarkGray, ColorRGBA.Yellow));
        
        node=carNode;
        //TODO: add CollisionShapes to CarParts
        node.attachChild(partsNode);
        ra.getRoot().attachChild(node);
        if(ra.bulletAppState!=null)ra.bulletAppState.getPhysicsSpace().add(this);
        return this;
    }
    
    public void detachPart(VehiclePart p){
        Spatial part = null;
        for(int i=0;i<partsNode.getChildren().size();i++){
            if(partsNode.getChildren().get(i).getName().equals(p.getSpatial().getName())){
                part = partsNode.getChildren().get(i);
                break;
            }
        }
        if(part!=null){
            Vector3f position = part.getWorldTranslation();
            Quaternion rotation = part.getWorldRotation();
            partsNode.detachChild(part);
            if(p instanceof Weapon){
                String name = p.name;
                for(int i=0;i<weapons.size();i++){
                    if(weapons.get(i).name.equals(name)){
                        weapons.remove(i);
                        break;
                    }
                    throw new Error("Couldnt remove weapon!!!");
                }
            }            
            ra.getRoot().attachChild(part);
            part.setLocalRotation(rotation);
            part.setLocalTranslation(position);
            
            part.setShadowMode(ShadowMode.CastAndReceive);
            
            part.addControl(new RigidBodyControl(new BoxCollisionShape(new Vector3f(.25f, .25f, .25f)),p.getWeight()));
            
            weight -= p.getWeight();
            updateMass();
            ra.bulletAppState.getPhysicsSpace().add(part);
            
            System.out.println("DETACHED!!!");
        }
    }
    
    /**
     * Adds a VehiclePart to the Car
     * @param vp the given VehiclePart containing a Node to be added
     * @param v the Position where to add the Part
     * @param rotation the Rotation of the Part when added
     * @return returns the Node that has just been added
     */
    public Spatial addPart(VehiclePart vp, Vector3f v, Quaternion rotation){
        Spatial vPart = vp.getSpatial().deepClone();
        if(vp instanceof Weapon){
            weapons.add((Weapon)vp);
        }
        vPart.setLocalTranslation(v);
        vPart.setLocalRotation(rotation);
        weight += vp.getWeight();
        updateMass();        
        partsNode.attachChild(vPart);
        return vPart;
    }
    
    private void updateMass(){
        mass = weight;
        //TODO: make mass changes to the car work !!!
        //Vector3f v = getAngularVelocity();
        //rebuildRigidBody();
        //applyCentralForce(v);
    }
    
    /**
     * steers the car with a given KeyMapping
     * @param name the Key"name" pressed ("Left","Right","Up","Reset"...)
     * @param isPressed boolean whether the key is pressed or not
     * @param tpf TimePerFrame for exact steering at any FPS
     */
    public void steerMe(String name, boolean isPressed, float tpf){
        if (name.equals("Left")) {
            if (isPressed) {
                steeringValue += .2f;
            } else {
                steeringValue += -.2f;
            }
            steer(steeringValue);
        } else if (name.equals("Right")) {
            if (isPressed) {
                steeringValue += -.2f;
            } else {
                steeringValue += .2f;
            }
            steer(steeringValue);
        } //note that our fancy car actually goes backwards..
        else if (name.equals("Up")) {
            if (isPressed) {
                accelerationValue -= getAcceleration();
            } else {
                accelerationValue += getAcceleration();
            }
            accelerate(accelerationValue);
            //player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, playerCar.carUrl)));
        }else if (name.equals("Down")) {
            if (isPressed) {
                accelerationValue += getAcceleration();
            } else {
                accelerationValue -= getAcceleration();
            }
            accelerate(accelerationValue);
            //player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, playerCar.carUrl)));
        
        }else if (name.equals("Brake")) {
            if (isPressed) {
                brake(400f);
            } else {
                brake(0f);
            }
        } else if (name.equals("Reset")) {
            if (isPressed) {
                System.out.println("Reset");
                setPhysicsLocation(new Vector3f(-15,10,0));
                setPhysicsRotation(new Matrix3f());
                setLinearVelocity(Vector3f.ZERO);
                setAngularVelocity(Vector3f.ZERO);
                resetSuspension();
            } else {
            }
        }else if(name.equals("nextWeapon")&&isPressed){ //switch to the next weapon
            if(selectedWeapon==weapons.size())
                selectedWeapon=0;
            else
                selectedWeapon++;
        }else if(name.equals("previousWeapon")&&isPressed){ //switch to the previous weapon
            if(selectedWeapon==0)
                selectedWeapon=weapons.size();
            else
                selectedWeapon--;
        }else if(name.equals("Shoot")){ //activate your weapon
            if(weapons.get(selectedWeapon).recharge>=weapons.get(selectedWeapon).maxRecharge&&weapons.get(selectedWeapon).loads!=0){
                weapons.get(selectedWeapon).onAction();
                weapons.get(selectedWeapon).recharge=0;
                if(weapons.get(selectedWeapon).loads!=-1)
                    weapons.get(selectedWeapon).loads--;
            }
        }
        for(int i=0;i<weapons.size();i++){ //update weapon recharges
            weapons.get(i).recharge+=tpf;
            if(weapons.get(i).recharge>weapons.get(i).maxRecharge)
                weapons.get(i).recharge=weapons.get(i).maxRecharge;
            if(weapons.get(i).loads==0)
                weapons.get(i).recharge = 0;
        }
    }
}